#include "Socket.hpp"
#include "InetAddr.hpp"
using namespace socket_ns;

static const int gnum = sizeof(fd_set) * 8; // select可以关注的 fd 最大数量
static const int defaultnum = -1;
class SelectServer
{

public:
    SelectServer(uint16_t port) : _port(port), _listensock(std::make_unique<TcpSoket>())
    {
        _listensock->BuildListenSocket(_port);
    }

    void Init()
    {
        // 循环，初始化 fd 数组
        for (int i = 0; i < gnum; i++)
            fd_array[i] = defaultnum;

        // 添加 listensock 到fd数组
        fd_array[0] = _listensock->Sockfd();
    }

    // 接收新的 sockfd
    void Accept()
    {
        // listen 就绪，表示新连接就绪，可以接收了
        InetAddr client;
        int sockfd = _listensock->Acceptor(&client);
        if (sockfd > 0)
        {
            LOG(INFO, "get a new link, addr: %s\n", client.AddrStr().c_str());
            // 接收新连接，对方不一定立即发送数据，还是需要 select 等待
            // 加入到 fd 数组，找空位
            bool flag = false;
            for (int i = 1; i < gnum; i++)
            {
                if (fd_array[i] == defaultnum)
                {
                    flag = true;
                    fd_array[i] = sockfd;
                    LOG(INFO, "add %d to fd_array\n", sockfd);
                    break;
                }
            }
            if (!flag)
            {
                // 没有空位了
                LOG(WARNING, "Server is full\n");
                ::close(sockfd);
            }
        }
    }

    // sockfd 读事件就绪，处理 IO
    void HandlerIO(int i)
    {
        // 普通 sockfd 就绪，可以进行 IO 处理了
        char readbuffer[1024];
        int n = ::recv(fd_array[i], readbuffer, sizeof(readbuffer) - 1, 0);
        if (n > 0)
        {
            readbuffer[n] = 0;
            std::cout << "client say: " << readbuffer;

            // 构建回显信息，一个简单的 http 响应
            std::string echo_info = "HTTP/1.0 200 OK\r\n";
            std::string content = "<html><body><h1>hello world!</h1></body></html>";

            echo_info += "Content_Type: text/html\r\n";
            echo_info += "Content_Length: " + std::to_string(content.size()) + "\r\n";
            echo_info += "\r\n";
            echo_info += content;

            ::send(fd_array[i], echo_info.c_str(), echo_info.size(), 0);
        }
        else if (n == 0)
        {
            LOG(INFO, "client quit\n");
            fd_array[i] == defaultnum;
            ::close(fd_array[i]);
        }
        else
        {
            LOG(ERROR, "recv error\n");
            fd_array[i] == defaultnum;
            ::close(fd_array[i]);
        }
    }

    // 大量 fd 就绪
    void HandlerEvent(fd_set &rfds)
    {
        // rfds 中记录了等待成功的 fd，使用fd数组来检查是哪一个fd就绪，循环
        // 事件派发
        for (int i = 0; i < gnum; i++)
        {
            if (fd_array[i] == defaultnum) continue;

            // 到这里，fd合法，检查哪一个合法fd就绪
            if (FD_ISSET(fd_array[i], &rfds))
            {
                // fd就绪，可能是 listensockfd，也可能是普通 sockfd
                LOG(DEBUG, "fd-%d is ready\n", fd_array[i]);
                if (fd_array[i] == _listensock->Sockfd())
                {
                    Accept();
                }
                else
                {
                    HandlerIO(i);
                }
            }
        }
    }

    void PrintFds()
    {
        std::cout << "rfds: ";
        for (int i = 0; i < gnum; i++)
            if (fd_array[i] != defaultnum)
                std::cout << fd_array[i] << " ";
        std::cout << std::endl;
    }
    void Loop()
    {
        while (1)
        {
            // _listensock->Acceptor(); // 不可以直接 Accept，如果一直不建立新链接，就会一直阻塞
            // 所以需要 select 等待，一旦建立新连接，等价于读事件就绪，可以直接Accept

            fd_set rfds;    // 输入输出型参数，每次都要重新设置
            FD_ZERO(&rfds); // 清空rfds集

            // 循环，将 fd 数组的 fd 加入到文件描述集
            int maxfd = defaultnum;
            for (int i = 0; i < gnum; i++)
            {
                // 设置文件描述符集
                if (fd_array[i] != defaultnum)
                {
                    FD_SET(fd_array[i], &rfds);
                }

                // 寻找最大 fd
                if (fd_array[i] > maxfd)
                    maxfd = fd_array[i];
            }
            struct timeval timeout = {5, 0}; // 设置超时时间

            // select
            int n = ::select(maxfd + 1, &rfds, nullptr, nullptr, nullptr); // time设置为 nul 就是阻塞等待
            switch (n)
            {
            case 0:
                // 超时
                LOG(DEBUG, "timeout, %d.%d\n", timeout.tv_sec, timeout.tv_usec);
                break;
            case -1:
                // 等待失败
                LOG(ERROR, "select error\n");
            default:
                // 等待成功
                LOG(DEBUG, "have event ready, n: %d\n", n);
                HandlerEvent(rfds);
                PrintFds();
                break;
            }
        }
    }

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listensock;
    int fd_array[gnum]; // 维护要关注的 fd
};